#include "wx/wxprec.h"
#include "wx/stackwalk.h"
#include "wx/stdpaths.h"
#include <execinfo.h>

#ifdef HAVE_CXA_DEMANGLE
#include <cxxabi.h>
#endif // HAVE_CXA_DEMANGLE

// ----------------------------------------------------------------------------
// tiny helper wrapper around popen/pclose()
// ----------------------------------------------------------------------------

class wxStdioPipe {
  public:
    // ctor parameters are passed to popen()
    wxStdioPipe( const char *command, const char *type ) {
      m_fp = popen( command, type );
    }

    // conversion to stdio FILE
    operator FILE *() const { return m_fp; }

    // dtor closes the pipe
    ~wxStdioPipe() {
      if( m_fp ) {
        pclose( m_fp );
      }
    }

  private:
    FILE *m_fp;

    DECLARE_NO_COPY_CLASS( wxStdioPipe )
};

void wxStackFrame::OnGetName() {
  if( !m_name.empty() ) {
    return;
  }
  wxString syminfo = wxString::FromAscii( m_syminfo );
  const size_t posOpen = syminfo.find( _T( '(' ) );
  if( posOpen != wxString::npos ) {
    const size_t posPlus = syminfo.find( _T( '+' ), posOpen + 1 );
    if( posPlus != wxString::npos ) {
      const size_t posClose = syminfo.find( _T( ')' ), posPlus + 1 );
      if( posClose != wxString::npos ) {
        if( m_name.empty() ) {
          m_name.assign( syminfo, posOpen + 1, posPlus - posOpen - 1 );
          #ifdef HAVE_CXA_DEMANGLE
          int rc = -1;
          char *cppfunc = __cxxabiv1::__cxa_demangle(m_name.mb_str(),NULL,NULL,&rc);
          if( rc == 0 ) {
            m_name = wxString::FromAscii( cppfunc );
          }
          free( cppfunc );
          #endif // HAVE_CXA_DEMANGLE
        }
        unsigned long ofs;
        if( wxString( syminfo, posPlus + 1, posClose - posPlus - 1 ).
            ToULong( &ofs, 0 ) ) {
          m_offset = ofs;
        }
      }
    }
    m_module.assign( syminfo, posOpen );
  } else {
    m_module = syminfo;
  }
}

#define MAX_FRAMES          200
#define CHARS_PER_FRAME    (sizeof(void*) * 2 + 3)
#define BUFSIZE            (50 + MAX_FRAMES*CHARS_PER_FRAME)

// static data
void *wxStackWalker::ms_addresses[MAX_FRAMES];
char **wxStackWalker::ms_symbols = NULL;
int wxStackWalker::m_depth = 0;
wxString wxStackWalker::ms_exepath;
static char g_buf[BUFSIZE];


void wxStackWalker::SaveStack( size_t maxDepth ) {
  maxDepth = wxMin( WXSIZEOF( ms_addresses ) / sizeof( void* ), maxDepth );
  m_depth = backtrace( ms_addresses, maxDepth * sizeof( void* ) );
  if( !m_depth ) {
    return;
  }
  ms_symbols = backtrace_symbols( ms_addresses, m_depth );
}

void wxStackWalker::ProcessFrames( size_t skip ) {
  wxStackFrame frames[MAX_FRAMES];
  if( !ms_symbols || !m_depth ) {
    return;
  }
  skip += 3;
  int towalk = InitFrames( frames, m_depth - skip, &ms_addresses[skip], &ms_symbols[skip] );
  for( int n = 0; n < towalk - ( int )skip; n++ ) {
    OnStackFrame( frames[n] );
  }
}

void wxStackWalker::FreeStack() {
  if( ms_symbols ) {
    free( ms_symbols );
  }
  ms_symbols = NULL;
  m_depth = 0;
}

int wxStackWalker::InitFrames( wxStackFrame *arr, size_t n, void **addresses, char **syminfo ) {
  wxString exepath = wxStackWalker::GetExePath();
  if( exepath.empty() ) {
    exepath = wxStandardPaths::Get().GetExecutablePath();
    if( exepath.empty() ) {
      wxLogDebug( wxT( "Cannot parse stack frame because the executable " )
                  wxT( "path could not be detected" ) );
      return 0;
    }
  }
  int len = snprintf( g_buf, BUFSIZE, "addr2line -C -f -e \"%s\"", ( const char* ) exepath.mb_str() );
  len = ( len <= 0 ) ? strlen( g_buf ) : len; // in case snprintf() is broken
  for( size_t i = 0; i < n; i++ ) {
    snprintf( &g_buf[len], BUFSIZE - len, " %p", addresses[i] );
    len = strlen( g_buf );
  }
  wxStdioPipe fp( g_buf, "r" );
  if( !fp ) {
    return 0;
  }
  wxString name, filename;
  unsigned long line = 0,curr = 0;
  for( size_t i = 0; i < n; i++ ) {
    if( fgets( g_buf, WXSIZEOF( g_buf ), fp ) ) {
      name = wxString::FromAscii( g_buf );
      name.RemoveLast(); // trailing newline
      if( name == _T( "??" ) ) {
        name.clear();
      }
    } else {
      wxLogDebug( _T( "cannot read addr2line output for stack frame #%lu" ),( unsigned long )i );
      return false;
    }
    if( fgets( g_buf, WXSIZEOF( g_buf ), fp ) ) {
      filename = wxString::FromAscii( g_buf );
      filename.RemoveLast();
      const size_t posColon = filename.find( _T( ':' ) );
      if( posColon != wxString::npos ) {
        wxString( filename, posColon + 1, wxString::npos ).ToULong( &line );
        filename.erase( posColon );
        if( filename == _T( "??" ) ) {
          filename.clear();
        }
      } else {
        wxLogDebug( _T( "Unexpected addr2line format: \"%s\" - " )_T( "the semicolon is missing" ),filename.c_str() );
      }
    }
    arr[curr++].Set( name, filename, syminfo[i], i, line, addresses[i] );
  }
  return curr;
}

void wxStackWalker::Walk( size_t skip, size_t maxDepth ) {
  SaveStack( maxDepth );
  ProcessFrames( skip );
  FreeStack();
}
